package plus.run;

import plus.Gripe;
import plus.M_System;

import java.io.*;

/**
 * You're looking at a very real compromise.
 * 
 * Sure, it would be nice to be able to compile PLUS code into JVM bytecode.
 * Trouble is I don't have the requiste knowledge to manufacture such, just yet,
 * and as you can imagine I was much more concerned with making it work at all
 * rather than carrying out a research-project on the JVM. In fact, I did spend
 * a bit of time looking into making .class files, but heavens it's complicated!
 * And it takes away from the essential task of getting something working.
 * 
 * Accordingly, I built this as a stepping stone. It is a simple virtual machine
 * (Yes, on Java, which itself runs on a virtual machine... Oh, the recursion!)
 * which has the essential characteristics in common with any typical real-world
 * CPU. The idea was I'd get my programs working on this imaginary CPU, and then
 * later it would be a comparatively minor task to translate each basic operation
 * for whatever real platform seemed like a good idea. (The hard part would be
 * creating a suitable run-time environent, and maybe cooperating with a linker
 * and an ABI for foreign function calls.)
 * 
 * Most instructions operate on the top few elements of a data stack. This is
 * represented by an array (of java.lang.Object) and a stack pointer. For local
 * variables, there is a frame pointer (called BP) and instructions to read and
 * write relative to it.
 * 
 * There is also an array of constants (those that aren't more compactly stored
 * in an instruction) and also the byte-array of compiled code.
 * 
 * The essential function here is "exec()" which, the interested reader will see,
 * gives absolutely no help to the matter of exceptions which may be thrown by
 * the Java runtime. In fact, exceptions are not presently a solved problem in
 * this implementation.
 * 
 * @author me
 *
 */
public final class VM {
	public static final byte BRK = 0; // Break
	public static final byte LDI = 1; // Load Small Integer (one signed byte)
	public static final byte LDC = 2; // Load Constant ("big")
	               // Integer   Real     String
	public static final byte ADI = 3,  ADR = 4, ADS = 5; // Add
	public static final byte SBI = 6,  SBR = 7; // Subtract
	public static final byte MLI = 8,  MLR = 9; // Multiply
	public static final byte DVI = 10, DVR = 11; // Divide
	public static final byte NGI = 12, NGR = 13; // Negate
	public static final byte MDI = 14, MDR = 15; // Modulus
	public static final byte NOT = 16, XOR = 17; // Logical operators
	
	public static final byte NEW = 18, STF = 19;
	public static final byte AND = 20, OR  = 21, EOR = 22, BNOT = 23; // Bitwise operations (on integers)
	// Relational operations:
	public static final byte LTI = 24, LEI = 25, EQI = 26, NEI = 27, GTI = 28, GEI = 29; // Integer
	public static final byte LTR = 30, LER = 31, EQR = 32, NER = 33, GTR = 34, GER = 35; // Real
	public static final byte LTS = 36, LES = 37, EQS = 38, NES = 39, GTS = 40, GES = 41; // String
	public static final byte JTR = 42, JFL = 43, JMP = 44; // Jump if true/false/always
	public static final byte CUP = 45; // Call user procedure
	public static final byte RET = 46; // Return from user procedure (lex level and number of args to drop)
	public static final byte ENT = 47; // Enter procedure (operand = stack adjust for local variables)
	public static final byte CSP = 48; // Call system procedure. (+module +proc id)
	public static final byte NUL = 49; // Load Java "null"
	public static final byte FLS = 50; // False
	public static final byte TRU = 51; // True
	
	public static final byte PUSHBP = 52;
	public static final byte READSTACK = 53;
	public static final byte WRITESTACK = 54;
	
	public static final byte LDX = 56; // Load at an index (into an array (or "record"))
	public static final byte STX = 57; // Store to an array index
	
	public static final byte LSR = 58; // Shift right with zeros
	public static final byte ASR = 59; // Shift right with sign extend
	public static final byte SHL = 60; // Shift left with zeros
	public static final byte ROR = 61; // Rotate right
	public static final byte ROL = 62; // Rotate left
	public static final byte SQRT = 63; // Sqrt
	public static final byte SIN = 64;
	public static final byte COS = 65;
	public static final byte TAN = 66;
	public static final byte ATN = 67;
	public static final byte EXP = 68; // e ^ x
	public static final byte LOG = 69; // Natural log
	public static final byte POW = 70; // x ^ y
	public static final byte POP = 71;
	public static final byte EQO = 72;
	public static final byte CTH = 73; // Call Thunk
	public static final byte MTH = 74; // Make Thunk
	public static final byte LEN = 75; // Take length of array.
	// remaining: conversions among intrinsic types. Although, there are MANY ways...
	// I expect many conversions are best done with functions. However, there are
	// probably grounds for something like "default" conversions.
	
	private static final int maxStack = 16384;
	private static final int maxCode = 65536;
	private static final int maxConst = 32768;
	private static final int WORDSIZE = 32;
	
	public static Object[] stack, constPool;
	public static int sp;	// Stack Pointer. Always points at the last pushed datum.
	static int pp;  // (constant) pool pointer. Next available constant.
	static int bp;	// Base Pointer
	static byte[] code = new byte[maxCode];
	public static int pc;
	private static int constPtr = -1;
	private static int nSysMods;
	private static M_System[] sysMods = new M_System[256];
	public static int newConst(Object c) { constPool[++constPtr] = c; return constPtr; }
	public static void attach(M_System sysMod) { sysMods[sysMod.sysModID = nSysMods++] = sysMod; }
	public static void reset() {
		stack = new Object[maxStack];
		constPool = new Object[maxConst]; 
		bp = sp = stack.length;
		pc = nSysMods = 0;
		constPtr = -1;
		Emit.org = 0;
	}
	private static final byte[] magic = {
		'P', 'L', 'U', 'S', '-', '1', '.', '0', 0
	};
	public static void bsave(String path) throws IOException {
		OutputStream file = new FileOutputStream(path);
		OutputStream buffer = new BufferedOutputStream(file);
		for (int i=0; i<magic.length; i++) buffer.write(magic[i]);
		wrint(buffer, Emit.org);
		buffer.write(code, 0, Emit.org);
		wrint(buffer, constPtr+1);
		ObjectOutput output = new ObjectOutputStream(buffer);
		for (int i=0; i<=constPtr; i++) {
			System.err.println(constPool[i]);
			output.writeObject(constPool[i]);
		}
		output.close();
	}
	private static void wrint(OutputStream os, int i) throws IOException {
		os.write(i>>>24); os.write(i>>>16); os.write(i>>>8); os.write(i>>0);
	}
	private static int rdint(InputStream ins) throws IOException {
		int k = 0;
		for (int i=0; i<4; i++) k = (k<<8) | ins.read();
		return k;
	}
	public static void bload(String path) throws IOException {
		InputStream ins = new BufferedInputStream(new FileInputStream(path));
		// Check magic string:
		for (int i=0; i<magic.length; i++) if (ins.read() != magic[i]) {
			Gripe.gripe("Found incorrect magic number.");
		}
		Emit.org = rdint(ins);
		ins.read(code, 0, Emit.org);
		constPtr = rdint(ins) - 1;
		ObjectInput input = new ObjectInputStream(ins);
		for (int i=0; i<=constPtr; i++) try {
			constPool[i] = input.readObject();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		input.close();
	}
	
	static void debug(String s) { System.err.print(s); }
	
	static void enter() { stack[--sp] = bp; bp = sp; }
	static void exit() { sp = bp; bp = (int) stack[sp++]; }
	public static Object pop() { return stack[sp++]; }
	public static void push(Object o) { stack[--sp] = o; }
	public static void alloc(int nr) { sp -= nr; }
	public static Object at(int o) { return stack[bp+o]; }
	public static void drop(int n) { sp += n; }
	private static int fetch() { return code[pc++]; }
	private static int u8() { return fetch() & 255; }
	private static int u16() { return u8() + (u8()<<8); }
	private static int big() { int a = u8(); return (a<128) ? a : (u8()<<7) + a - 128; }
	static int u8(int a) { return code[a] & 255; }
	static int u16(int a) { return u8(a) | (u8(a+1)<<8); }
	static void stuff16(int a, int v) { code[a] = (byte)(v&255); code[a+1] = (byte)(v>>8); }
	
	public static void exec() {
		try { _exec(); }
		catch (Throwable t) {
			System.err.println("=== Break to Monitor ===");
			System.err.println("PC = "+pc);
			System.err.println("-- Stack --");
			while (sp<maxStack) {
				System.err.println(stack[sp++]);
			}
			throw(t);
		}
	}
	/**
	 * a quick and dirty way to checksum the code segment.
	 * does nothing at all for the constant pool.
	 * @return
	 */
	public static int checksum() {
		int c1 = 5, c2 = 7;
		for (int i=0; i<Emit.org; i++) {
			c1 += code[i];
			c2 += c1;
		}
		return c1 ^ c2;
	}
	private static void _exec() {
//		System.err.println("-- Constants --");
//		for (int i=0; i<=constPtr; i++) System.err.println(i+": "+constPool[i]);
		int I;
		double D;
		String S;
		Object[] A;
		Object O;
		boolean B;
		while (true) {
			//debug("\n"+pc+": ");
			switch (code[pc++]) {
			case BRK: return; // exit VM
			case LDI: push(fetch()); break;  
			case LDC: push(constPool[big()]); break;
			case ADI: I = (int)stack[sp++]; stack[sp] = (int)stack[sp] + I; break;
			case ADR: D = (double)stack[sp++]; stack[sp] = (double)stack[sp] + D; break;
			case ADS: S = (String)stack[sp++]; stack[sp] = (String)stack[sp] + S; break;
			case SBI: I = (int)stack[sp++]; stack[sp] = (int)stack[sp] - I; break;
			case SBR: D = (double)stack[sp++]; stack[sp] = (double)stack[sp] - D; break;
			case MLI: I = (int)stack[sp++]; stack[sp] = (int)stack[sp] * I; break;
			case MLR: D = (double)stack[sp++]; stack[sp] = (double)stack[sp] * D; break;
			case DVI: I = (int)stack[sp++]; stack[sp] = (int)stack[sp] / I; break;
			case DVR: D = (double)stack[sp++]; stack[sp] = (double)stack[sp] / D; break;
			case NGI: stack[sp] = -(int)stack[sp]; break;
			case NGR: stack[sp] = -(double)stack[sp]; break;
			case MDI: I = (int)stack[sp++]; stack[sp] = (int)stack[sp] % I; break;
			case MDR: D = (double)stack[sp++]; stack[sp] = (double)stack[sp] % D; break;
			case NOT: stack[sp] = !(boolean)stack[sp]; break;
			case NEW: I = (int)stack[sp]; stack[sp] = new Object[I]; break;
			case STF:
				O = stack[sp++];
				I = (Integer)stack[sp];
				A = (Object[])stack[sp+1];
				A[I] = O;
				stack[sp] = I+1;
				break;
			case XOR: B = (boolean)stack[sp++]; stack[sp] = (boolean)stack[sp] ^ B; break;
			case AND: I = (int)stack[sp++]; stack[sp] = (int)stack[sp] & I; break;
			case OR : I = (int)stack[sp++]; stack[sp] = (int)stack[sp] | I; break;
			case EOR: I = (int)stack[sp++]; stack[sp] = (int)stack[sp] ^ I; break;
			case BNOT: stack[sp] = ~ (int)stack[sp]; break;
			case LTI: I = (int)stack[sp++]; stack[sp] = (int)stack[sp] <  I; break;
			case LEI: I = (int)stack[sp++]; stack[sp] = (int)stack[sp] <= I; break;
			case EQI: I = (int)stack[sp++]; stack[sp] = (int)stack[sp] == I; break;
			case NEI: I = (int)stack[sp++]; stack[sp] = (int)stack[sp] != I; break;
			case GTI: I = (int)stack[sp++]; stack[sp] = (int)stack[sp] >  I; break;
			case GEI: I = (int)stack[sp++]; stack[sp] = (int)stack[sp] >= I; break;
			case LTR: D = (double)stack[sp++]; stack[sp] = (double)stack[sp] <  D; break;
			case LER: D = (double)stack[sp++]; stack[sp] = (double)stack[sp] <= D; break;
			case EQR: D = (double)stack[sp++]; stack[sp] = (double)stack[sp] == D; break;
			case NER: D = (double)stack[sp++]; stack[sp] = (double)stack[sp] != D; break;
			case GTR: D = (double)stack[sp++]; stack[sp] = (double)stack[sp] >  D; break;
			case GER: D = (double)stack[sp++]; stack[sp] = (double)stack[sp] >= D; break;
			case LTS: S = (String)stack[sp++]; stack[sp] = ((String)stack[sp]).compareTo(S) <  0; break;
			case LES: S = (String)stack[sp++]; stack[sp] = ((String)stack[sp]).compareTo(S) <= 0; break;
			case EQS: S = (String)stack[sp++]; stack[sp] = ((String)stack[sp]).compareTo(S) == 0; break;
			case NES: S = (String)stack[sp++]; stack[sp] = ((String)stack[sp]).compareTo(S) != 0; break;
			case GTS: S = (String)stack[sp++]; stack[sp] = ((String)stack[sp]).compareTo(S) >  0; break;
			case GES: S = (String)stack[sp++]; stack[sp] = ((String)stack[sp]).compareTo(S) >= 0; break;
			case JTR: I = u16(); if ((boolean)stack[sp]) pc = I; break;
			case JFL: I = u16(); if (!(boolean)stack[sp]) pc = I; break;
			case JMP: pc = u16(); break;
			case CUP: I = u16(); stack[--sp] = pc; pc = I; break;
			case RET:
				I = u8(); // Grab the operand (= number of words to drop).
				sp = bp; // Set SP to the indicated base pointer.
				bp = (int)stack[sp++]; // Pop the old base pointer.
				pc = (int)stack[sp++]; // Pop the program counter.
				sp += I; // Pop the arguments. (This is the "callee cleans" protocol.)
				break;
			case ENT:
				stack[--sp] = bp;
				bp = sp;
				sp -= u8();
				break;
			case CSP: I = u8(); sysMods[I].callSysProc(u8()); break;
			
			case NUL: stack[--sp] = null; break;
			case FLS: stack[--sp] = false; break;
			case TRU: stack[--sp] = true; break;
			
			case PUSHBP: stack[--sp] = bp; break;
			case READSTACK: stack[sp] = stack[(int)stack[sp] + fetch()]; break;
			case WRITESTACK: I = (int)stack[sp++]+fetch(); stack[I] = stack[sp++]; break;
			
			case LDX: I = (int)stack[sp++]; stack[sp] = ((Object[])stack[sp])[I]; break;
			case STX:
				I = (int)stack[sp++];
				O = stack[sp++];
				A = (Object[])stack[sp];
				A[I] = O;
				break;
			case LSR: I = (int)stack[sp++]; stack[sp] = (int)stack[sp] >>> I; break;
			case ASR: I = (int)stack[sp++]; stack[sp] = (int)stack[sp] >> I; break;
			case SHL: I = (int)stack[sp++]; stack[sp] = (int)stack[sp] << I; break;
			case ROR: I = (int)stack[sp++]; stack[sp] = ((int)stack[sp] >>> I) | ((int)stack[sp] << (WORDSIZE-I)); break;
			case ROL: I = (int)stack[sp++]; stack[sp] = ((int)stack[sp] << I) | ((int)stack[sp] >>> (WORDSIZE-I)); break;
			case SQRT:stack[sp] = Math.sqrt((double)stack[sp]); break;
			case SIN: stack[sp] = Math.sin((double)stack[sp]); break;
			case COS: stack[sp] = Math.cos((double)stack[sp]); break;
			case TAN: stack[sp] = Math.tan((double)stack[sp]); break;
			case ATN: stack[sp] = Math.atan((double)stack[sp]); break;
			case EXP: stack[sp] = Math.exp((double)stack[sp]); break;
			case LOG: stack[sp] = Math.log((double)stack[sp]); break;
			case POW: D = (double)stack[sp++]; stack[sp] = Math.pow((double)stack[sp], D); break;
			case POP: sp++; break;
			case EQO: O = stack[sp++]; stack[sp] = stack[sp] == O; break;
			case CTH: ((Thunk)stack[sp++]).jsr(); break;
			case MTH: // Make Thunk:
				push(new Thunk(bp, u16()));
				break;
			case LEN: stack[sp] = ((Object[])stack[sp]).length; break;
			default: throw new RuntimeException("Bad bytecode. Sorry!"); 
			}
		}
	}
	public static void call(int entryPoint) {
		push(pc);
		pc = entryPoint;
	}
	
}
